package game;

import exception.IllegalDistanceException;
import be.kuleuven.cs.som.annotate.*;
import values.Vector;

/**
 * A class of bullets involving a source and a density.
 * This class of bullets extends the class of space objects.
 * @invar	| hasProperSource(getSource())
 * @author JeroenGillis && ThomasProvoost 
 */
public class Bullet extends SpaceObject {
	
	private static final double DENSITY = 7.8*Math.pow(10, 12);
	private final Ship source;
	private boolean hasHitBoundary = false;

	/**
	 * Create a new bullet with given source, x position, y position, x velocity, y velocity, radius, maxVelocity and world.
	 * @effect	| super(xPosition, yPosition, xVelocity, yVelocity, radius, maxVelocity,world)
	 * @post	| (new this).getSource() == source
	 * @throws	IllegalArgumentException
	 * 			| ! isValidSource(source)
	 */
	public Bullet(Ship source, double xPosition, double yPosition, double xVelocity, double yVelocity, double radius, double maxVelocity,World world) 
			throws IllegalArgumentException {
		super(xPosition, yPosition, xVelocity, yVelocity, radius, maxVelocity,world,calculateMass(radius));
		if(!isValidSource(source)) {
			throw new IllegalArgumentException();
		}
		this.source = source;
	}
	
	/**
	 * Create a new bullet with given source, position vector, velocity vector, radius, maxVelocity and world.
	 * @effect	| this(source,position.getxComp(),position.getyComp(),velocity.getxComp(),
	 * 					velocity.getyComp(),radius,maxVelocity, world)
	 */
	public Bullet(Ship source, Vector position, Vector velocity, double radius, double maxVelocity,World world) {
		this(source,position.getxComp(),position.getyComp(),velocity.getxComp(),velocity.getyComp(),radius,maxVelocity, world);
	}
	
	/**
	 * Return the source of this bullet
	 */
	@Basic @Immutable
	public Ship getSource() {
		return source;
	} 
	
	/**
	 * Return a boolean reflecting whether the given source is a valid source for this bullet.
	 * @param object
	 * 			The object to be checked.
	 * @return	| if(source == null) result == false
	 * 			| if(! (object instanceof Ship)) result == false
				| if(source.isTerminated() ^ this.isTerminated()) result == false 
				| if(source.getWorld() != this.getWorld()) result == false
				| else result == true
	 */
	public boolean isValidSource(SpaceObject object) {
		if(object == null) return false;
		if(! (object instanceof Ship)) return false;
		if(object.isTerminated() ^ this.isTerminated()) return false;
		if(object.getWorld() != this.getWorld()) return false;
		return true;
	}
	
	/**
	 * Return a boolean reflecting whether this bullet has a proper source.
	 * @return
	 */
	public boolean hasProperSource() {
		return ( this.isValidSource(getSource()) && (getSource().containsBullet(this)));
	}
	
	/**
	 * Calculate the mass of this bullet.
	 * @return	| result == 4.0/3.0*Math.pow(getRadius(), 3)*Math.PI*Bullet.DENSITY
	 */
	public static double calculateMass(double radius) {
		return 4.0/3.0*Math.pow(radius, 3)*Math.PI*Bullet.DENSITY;
	}

	/**
	 * Return a boolean reflecting whether this bullet has hit a boundary of the world it is in.
	 */
	@Basic
	public boolean hasHitBoundary() {
		return hasHitBoundary;
	}

	/**
	 * This bullet hits a boundary of the world it is in.
	 * @post	| if(this.hasHitBoundary()) (new this).isTerminated()
	 * @post	| (new this).hasHitBoundary() == true
	 * @effect	| if(!hasHitBoundary()) super.hitBoundary() 
	 */
	@Override
	public void hitBoundary() {
		if(hasHitBoundary())
			terminate();
		else {
			super.hitBoundary();
			this.hasHitBoundary = true;
		}
	}
	
	/**
	 * Terminate this bullet.
	 * @post	| (new this.getSource()).containsBullet(this) == false
	 * @post	| (new this).getSource() == null
	 * @effect	| super.terminate()
	 */
	@Override
	public void terminate() {
		Ship source = this.getSource();
		if(source != null) {
			source.removeAsBullet(this);
		}
		super.terminate();
	}
	
	/**
	 * This bullet collides with the given other object.
	 * @effect	| super.collideWith(otherObject)
	 * @effect	| if(isFatalCollision(otherObject)) 
	 *			|		otherObject.terminate()
	 *			|		this.terminate()
	 */
	@Override
	public void collideWith(SpaceObject otherObject) throws IllegalArgumentException, IllegalStateException {
		super.collideWith(otherObject);
		if(isFatalCollision(otherObject)) {
			otherObject.terminate();
			this.terminate();
		}
		
	}
	
	/**
	 * Return a boolean reflecting whether a collision between this bullet and the other object is fatal for both.
	 * @param otherObject
	 * 			The other object to be checked.
	 * @return	| result == this.getSource() != otherObject
	 */
	public boolean isFatalCollision(SpaceObject otherObject) {
		if(otherObject == this)
			return false;
		else
			return this.getSource() != otherObject;
	}
	
	/**
	 * A method returning the time to collision of this bullet with the given SpaceObject.
	 * @param	otherObject
	 * 			The object of which the time to collision with this bullet needs to be calculated.
	 * @return	| if this.getSource() == otherObject
	 * 			|		result == Double.POSITIVE_INFINITY
	 * 			| else  result == super.getTimeToCollision(otherObject)
	 */
	@Override
	public double getTimeToCollision(SpaceObject otherObject) throws NullPointerException,
	IllegalDistanceException, IllegalStateException{
		if(this.getSource() == otherObject)
			return Double.POSITIVE_INFINITY;
		else
			return super.getTimeToCollision(otherObject);
	}
	
}
